/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.askerov.dynamicgrid;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.ComponentTransition;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.Image;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.ScrollView;
import ohos.agp.components.Text;
import ohos.agp.utils.MimeData;
import ohos.agp.utils.Point;
import ohos.agp.utils.Rect;
import ohos.agp.window.service.Display;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * The DragLayout
 *
 * @since 2021-05-15
 */
public class DragGridView extends DirectionalLayout {
    private static final String UP_GRID_TAG = "upGrid";
    private static final String DOWN_GRID_TAG = "downGrid";
    private static final int INVALID_POSITION = -1;

    private boolean isViewOnExchange;
    private boolean isScroll;
    private GridView parentView;
    private ScrollView scrollView;

    private boolean isBack;

    // Item when dragged
    private int scrollViewTop;
    private int scrollViewLeft;
    private Component selectedView;
    private Component slice;

    private boolean isViewOnDrag;

    private final Map<Component, AnimatorProperty> animatorList = new HashMap<>();
    private int duration = 320;
    private EventHandler eventHandler = new EventHandler(EventRunner.getMainEventRunner());

    private List<GridItemInfo> upperItemList = new ArrayList<>();

    private float alphaFloat = 0.8f;
    private int animationRotate = 6;
    private int column = 3;

    private int lastPointY;

    private Display display = DisplayManager.getInstance().getDefaultDisplay(getContext()).get();
    private Point point = new Point();
    private int displayHeight;
    private int currentDragY;

    private final Component.LongClickedListener longClickListener =
        component -> {
            Component shadowComponent = getShadow();
            shadowComponent.setWidth(component.getWidth());
            shadowComponent.setHeight(component.getHeight());
            shadowComponent.setAlpha(alphaFloat);
            Component.DragFeedbackProvider dragFeedbackProvider =
                new Component.DragFeedbackProvider(shadowComponent);
            component.startDragAndDrop(new MimeData(), dragFeedbackProvider);
            component.setVisibility(Component.INVISIBLE);
            selectedView = component;
            isViewOnDrag = true;
            if (UP_GRID_TAG.equals(selectedView.getTag())) {
                if (slice.findComponentById(ResourceTable.Id_grid_view_up) instanceof GridView) {
                    parentView = (GridView) slice.findComponentById(ResourceTable.Id_grid_view_up);
                    startAnimation(parentView, animationRotate);
                    isBack = false;
                }
            }
            bindComponentTransition();
        };

    private ClickedListener clickedListener = new ClickedListener() {
        @Override
        public void onClick(Component component) {
            Text textItem = (Text) component.findComponentById(ResourceTable.Id_grid_item_text);
            Toast.show(getContext(), textItem.getText());
        }
    };

    /**
     * 构造方法
     *
     * @param context
     */
    public DragGridView(Context context) {
        this(context, null, "");
    }

    /**
     * 构造方法
     *
     * @param context
     * @param attrSet
     */
    public DragGridView(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    /**
     * 构造方法
     *
     * @param context
     * @param attrSet
     * @param styleName
     */
    public DragGridView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
    }

    /**
     * 设置数据
     *
     * @param list
     */
    public void setData(List<GridItemInfo> list) {
        this.upperItemList = list;
        initGridView();
    }

    /**
     * 设置列数
     *
     * @param column
     */
    public void setColumn(int column) {
        this.column = column;
    }

    /**
     * method for init view
     */
    public void initGridView() {
        slice = LayoutScatter.getInstance(getContext()).parse(ResourceTable.Layout_dynamicgrid_main, this, true);
        if (slice.findComponentById(ResourceTable.Id_grid_layout) instanceof ScrollView) {
            scrollView = (ScrollView) slice.findComponentById(ResourceTable.Id_grid_layout);
        }
        initUpListItem();
        initEventListener();
    }

    private void initUpListItem() {
        GridView gridView = (GridView) slice.findComponentById(ResourceTable.Id_grid_view_up);
        GridAdapter adapter = new GridAdapter(slice.getContext(), upperItemList, this.column);
        gridView.setColumnCount(this.column);
        gridView.setAdapter(adapter, longClickListener, clickedListener);
        gridView.setTag(UP_GRID_TAG);
    }

    private void initEventListener() {
        scrollView.setTouchEventListener(
            (component, touchEvent) -> {
                MmiPoint downScreenPoint = touchEvent.getPointerScreenPosition(touchEvent.getIndex());
                switch (touchEvent.getAction()) {
                    case TouchEvent.PRIMARY_POINT_DOWN:
                        currentDragY = (int) downScreenPoint.getY();
                        MmiPoint downPoint = touchEvent.getPointerPosition(touchEvent.getIndex());
                        scrollViewTop = (int) downScreenPoint.getY() - (int) downPoint.getY();
                        scrollViewLeft = (int) downScreenPoint.getX() - (int) downPoint.getX();
                        display.getSize(point);
                        displayHeight = (int) point.getPointY();
                        return true;
                    case TouchEvent.PRIMARY_POINT_UP:
                        isScroll = false;
                        AnimatorProperty animatorProperty = animatorList.get(selectedView);
                        if (animatorProperty != null && !isBack) {
                            animatorProperty.setStateChangedListener(null);
                            animatorProperty.stop();
                            animatorProperty.rotate(0).setDuration(duration).start();
                            startAnimation(selectedView, animationRotate);
                        }
                    case TouchEvent.CANCEL:
                        if (isViewOnDrag) {
                            selectedView.setScale(1.0f, 1.0f);
                            selectedView.setAlpha(1.0f);
                            selectedView.setVisibility(Component.VISIBLE);
                            isViewOnDrag = false;
                            isScroll = false;
                            return true;
                        }
                        break;
                    case TouchEvent.POINT_MOVE:
                        return printMove(downScreenPoint);
                    default:
                        break;
                }
                return false;
            });
    }

    private boolean printMove(MmiPoint downScreenPoint) {
        if (!isViewOnDrag) {
            return true;
        }
        int pointX = (int) downScreenPoint.getX();
        int pointY = (int) downScreenPoint.getY();
        this.exchangeItem(pointX, pointY);
        if (UP_GRID_TAG.equals(selectedView.getTag())) {
            this.swapItems(pointX, pointY);
        }
        int height = displayHeight - (displayHeight - 400);
        if (pointY < height) {
            if (lastPointY < pointY) {
                if (eventHandler != null) {
                    eventHandler.removeAllEvent();
                    lastPointY = 0;
                    return true;
                }
            }
        } else {
            if (lastPointY > pointY) {
                if (eventHandler != null) {
                    eventHandler.removeAllEvent();
                    lastPointY = 0;
                    return true;
                }
            }
        }
        this.handleScroll(pointY);
        lastPointY = pointY;
        return true;
    }

    private void exchangeItem(int pointX, int pointY) {
        if (!isStartupExchage(pointY)) {
            return;
        }
        GridView gridView;
        parentView.removeComponent(selectedView);
        int addPosition;
        gridView = (GridView) slice.findComponentById(ResourceTable.Id_grid_view_up);
        if (UP_GRID_TAG.equals(selectedView.getTag())) {
            selectedView.setTag(DOWN_GRID_TAG);
            addPosition = 0;
        } else {
            selectedView.setTag(UP_GRID_TAG);
            addPosition = gridView.getChildCount();
        }
        gridView.addComponent(selectedView, addPosition);
        selectedView = gridView.getComponentAt(addPosition);
        parentView = gridView;
        currentDragY = pointY;
    }

    private void swapItems(int pointX, int pointY) {
        if (isViewOnExchange) {
            isViewOnExchange = false;
            return;
        }
        int currentPosition = parentView.getChildIndex(selectedView);
        int endPosition = this.pointToPosition(pointX, pointY);
        if (endPosition == INVALID_POSITION || endPosition == currentPosition) {
            return;
        }
        parentView.removeComponent(selectedView);
        parentView.addComponent(selectedView, endPosition);
        currentDragY = pointY;
        selectedView = parentView.getComponentAt(endPosition);
    }

    private int pointToPosition(int pointX, int pointY) {
        int currentOffset = 100;
        int currentX = pointX - scrollViewLeft - currentOffset;
        int currentY = pointY + scrollView.getScrollValue(Component.VERTICAL) - scrollViewTop - currentOffset;
        int childCount = parentView.getChildCount();
        for (int i = 0; i < childCount; i++) {
            Rect child = parentView.getComponentAt(i).getComponentPosition();
            if (child.isInclude(currentX, currentY)) {
                return i;
            }
        }
        return INVALID_POSITION;
    }

    private void handleScroll(int pointY) {
        int scrollUp = -300;
        int scrollDown = 300;
        if (pointY < displayHeight - (displayHeight - 450)) {
            isScroll = true;
            scroll(scrollUp);
        } if (pointY > displayHeight - 300) {
            isScroll = true;
            scroll(scrollDown);
        }
    }


    private void scroll(int dy) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                eventHandler.removeAllEvent();
                if (isScroll) {
                    scroll(dy);
                    scrollView.fluentScrollByY(dy);
                }
            }
        };
        eventHandler.postTask(runnable, 200);
    }


    private void bindComponentTransition() {
        if (parentView != null && parentView.getComponentTransition() == null) {
            ComponentTransition transition = new ComponentTransition();
            transition.removeTransitionType(ComponentTransition.SELF_GONE);
            transition.removeTransitionType(ComponentTransition.OTHERS_GONE);
            transition.removeTransitionType(ComponentTransition.CHANGING);
            parentView.setComponentTransition(transition);
        }
    }

    private Component getShadow() {
        Component itemLayout =
            LayoutScatter.getInstance(slice.getContext()).parse(ResourceTable.Layout_grid_item, null, false);
        if (itemLayout.findComponentById(ResourceTable.Id_grid_item_image) instanceof Image) {
            Image imageItem = (Image) itemLayout.findComponentById(ResourceTable.Id_grid_item_image);
            imageItem.setPixelMap(ResourceTable.Media_icon);
            imageItem.setScale(1.2f, 1.2f);
        }
        return itemLayout;
    }

    private boolean isStartupExchage(int pointY) {
        int scrollY = isScroll ? scrollView.getScrollValue(Component.VERTICAL) : 0;
        int offsetY = pointY - currentDragY;
        offsetY = offsetY < 0 ? offsetY - scrollY : offsetY + scrollY;
        Rect currentRect = selectedView.getComponentPosition();
        int curOffsetY = currentRect.getCenterY() + offsetY;
        if (UP_GRID_TAG.equals(selectedView.getTag())) {
            isViewOnExchange = curOffsetY > parentView.getComponentPosition().bottom;
        }
        return isViewOnExchange;
    }

    /**
     * 取消动画
     */
    public void resetAnimation() {
        animatorList.forEach((component, animatorProperty) -> {
            animatorProperty.setStateChangedListener(null);
            animatorProperty.stop();
            animatorProperty.rotate(0).setDuration(duration).start();
        });
    }

    /**
     * 开始动画
     *
     * @param gridView
     * @param rotate
     */
    private void startAnimation(ComponentContainer gridView, int rotate) {
        resetAnimation();
        animatorList.clear();
        int childCount = gridView.getChildCount();
        for (int i = 0; i < childCount; i++) {
            AnimatorProperty animatorProperty = new AnimatorProperty();
            animatorProperty.setTarget(gridView.getComponentAt(i));
            if (i % 2 == 0) {
                animatorProperty.rotate(rotate).setDuration(duration);
            } else {
                animatorProperty.rotate(-rotate).setDuration(duration);
            }
            animatorProperty.setStateChangedListener(new Animator.StateChangedListener() {
                @Override
                public void onStart(Animator animator) {

                }

                @Override
                public void onStop(Animator animator) {

                }

                @Override
                public void onCancel(Animator animator) {

                }

                @Override
                public void onEnd(Animator animator) {
                    float rotation = animatorProperty.getTarget().getRotation();
                    animatorProperty.rotate(-rotation).setDuration(duration);
                    animatorProperty.start();
                }

                @Override
                public void onPause(Animator animator) {

                }

                @Override
                public void onResume(Animator animator) {

                }
            });
            animatorProperty.start();
            animatorList.put(gridView.getComponentAt(i), animatorProperty);
        }
    }

    /**
     * 单独启动其中一个的动画
     *
     * @param component
     * @param rotate
     */
    private void startAnimation(Component component, int rotate) {
        AnimatorProperty animatorProperty = new AnimatorProperty();
        animatorProperty.setTarget(component);
        animatorProperty.rotate(-rotate).setDuration(duration);
        animatorProperty.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
            }

            @Override
            public void onStop(Animator animator) {
            }

            @Override
            public void onCancel(Animator animator) {
            }

            @Override
            public void onEnd(Animator animator) {
                float rotation = animatorProperty.getTarget().getRotation();
                animatorProperty.rotate(-rotation).setDuration(duration);
                animatorProperty.start();
            }

            @Override
            public void onPause(Animator animator) {
            }

            @Override
            public void onResume(Animator animator) {
            }
        });
        animatorProperty.start();
        animatorList.put(component, animatorProperty);
    }

    /**
     * 返回标识
     *
     * @return 返回标识
     */
    public boolean isBack() {
        return isBack;
    }

    /**
     * 设置返回标识
     *
     * @param back
     */
    public void setBack(boolean back) {
        isBack = back;
    }
}
